package com.livelab.java;

import com.livelab.model.AssignedExercise;
import com.livelab.model.Exercise;
import com.livelab.util.CommandSynchronized;
import com.livelab.util.CommandSynchronized.Output;
import com.livelab.util.Constant;
import com.livelab.util.MySQLDatabaseUtil;
import com.livelab.util.Utility;
import com.livelab.util.RunResult;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;

/**
 * Backing bean that supports "examineGradeExercise.xhtml"
 * 
 * @author Dakota
 */
@ManagedBean
@RequestScoped
public class GradeExerciseBean {
    /* getters and setters */
    private AssignedExercise exercise;              //exercise details pulled from ExercisedAssigned in db
    
    private double totalScore;                      //student's score for exercise selected
    
    private Date date;                              //extention date selected

    private Exercise assignment;                    //exercise details pulled from ExerciseDescription in db

    private File sourceFile, classFile;             //file objects to point to .java and .class file for exercise
        
    private String assignedItem;                    //exercise name selected from assigned
    private String content;                         //content of .java file
    private String studentChosen;                   //student username value chosen from students

    /* getters */
    private boolean isSet, isExecuted, isChosen;
        
    private TreeMap<String, String> assigned;
    private TreeMap<String, String> students;
    
    private String run1, run2, run3;                 //run results of exercise test runs
    private MySQLDatabaseUtil db;                    //database  utility
    private PreparedStatement ps;
    private String exerciseName;                     //name of the exercise
    private String path;                             //path to exercise in file structure
    
    @ManagedProperty(value = "#{instructorLoginBean}")
    private InstructorLoginBean instructor;          //logged-in instructor information

    /**
     * Constructor
     */
    public GradeExerciseBean() {
        isSet = false;
        isExecuted = false;
        isChosen = false;
        db = new MySQLDatabaseUtil("localhost/javalivelab9e", "agsuser", "agsuser");

        assigned = new TreeMap<>();
        students = new TreeMap<>();
        assignment = new Exercise("", "", false, "", "", "", "", "", "");
    }

    @PostConstruct
    public void init() {
        //get the instructor associated with course id
        try {
            //populate student selection map
            try (ResultSet selectStudents = db.executeQuery("SELECT firstName, lastName, username FROM agsstudent WHERE courseId = '" + instructor.getCourseId() + "';")) {
                while (selectStudents.next()) {
                    students.put(selectStudents.getString("firstName") + " " + selectStudents.getString("lastName"), selectStudents.getString("username"));
                }
            }
            
            //populate exercise selection map
            try (ResultSet selectExercises = db.executeQuery("SELECT exerciseName, dueDate FROM exerciseassigned WHERE username = '" + instructor.getUsername() + "';")) {
                while (selectExercises.next()) {
                    assigned.put(selectExercises.getString("exerciseName") + " Due: " + selectExercises.getTimestamp("dueDate").toString(), selectExercises.getString("exerciseName"));
                }
            }
        } catch (SQLException ex) {
        }

        ps = db.getPreparedStatement("INSERT INTO agslog(username, exerciseName, comment, score, lastRun) VALUES (?,?,?,?,?);");
    }

    /*
     * GETTERS
     */
    
    public Exercise getAssignment() {
        return assignment;
    }

    public Map<String, String> getAssignedValue() {
        return assigned;
    }

    public Map<String, String> getStudentsValue() {
        return students;
    }

    public String getAssignedItem() {
        return assignedItem;
    }

    public String getStudentChosen() {
        return studentChosen;
    }

    public String getContent() {
        return content;
    }

    public boolean getIsExecuted() {
        return isExecuted;
    }

    public boolean getIsSet() {
        return isSet;
    }

    public boolean getIsChosen() {
        return isChosen;
    }

    public String getRun1() {
        return run1;
    }

    public String getRun2() {
        return run2;
    }

    public String getRun3() {
        return run3;
    }

    public double getScore() {
        return totalScore;
    }
    
    
    public Date getDate() {
        return date;
    }

    public AssignedExercise getExercise() {
        return exercise;
    }

    /*
     * SETTERS
     */
    
    public void setExercise(AssignedExercise e) {
        this.exercise = e;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public void setAssignment(Exercise e) {
        this.assignment = e;
    }

    public void setAssignedItem(String item) {
        this.assignedItem = item;
    }

    public void setStudentChosen(String item) {
        this.studentChosen = item;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setScore(double totalScore) {
        this.totalScore = totalScore;
    }

    public void setInstructor(InstructorLoginBean instructor) {
        this.instructor = instructor;
    }

    /*
     * ACTIONS
     */
    
    /**
     * Selecting an exercise from the assigned exercise list
     */
    public void chooseExercise() {
        isExecuted = false;

        FacesContext context = FacesContext.getCurrentInstance();
        
        //if student and exercise were chosen
        if (assignedItem != null && assignedItem.length() > 0 && studentChosen != null && studentChosen.length() > 0) {
            isSet = true;
            isChosen = true;
            exerciseName = assignedItem;
            //set the path to student's home directory
             path = Constant.AGS_ROOT + instructor.getCourseId() + "/" + studentChosen + "/";

            //.java file location
            sourceFile = new File(path + exerciseName + Constant.JAVA_EXT);

            if (sourceFile.exists()) {
                //read the source file into contents
                try {
                    content = new Scanner(sourceFile).useDelimiter("\\A").next();
                } catch (FileNotFoundException ex) {
                }
            //if the source file doesn't exist, create template
            } else {
                content = "/*\n * Name: " + studentChosen + "\n"
                        + " */\n\n"
                        + "public class " + exerciseName + "{\n\t"
                        + "public static void main(String[] args){\n\t\t"
                        + "//insert code in this template. Name class " + exerciseName + "\n\t"
                        + "}\n"
                        + "}";
            }

            try {
                //select the exercise information from default exercise
                ResultSet rs1;
                rs1 = db.executeQuery("SELECT * FROM  defaultexercise WHERE exerciseName = '" + assignedItem + "';");
                if (rs1.next()) {
                    assignment = new Exercise(rs1.getString("exerciseName"), rs1.getString("exerciseDescription"), rs1.getBoolean("gradable"), rs1.getString("inputA"), rs1.getString("inputB"), rs1.getString("inputC"), rs1.getString("outputA"), rs1.getString("outputB"), rs1.getString("outputC"));
                    context.addMessage(null, new FacesMessage("Populating " + assignedItem, "Complete"));
                }
                rs1.close();

                //select thie exercise information from exerciseassigned
                ResultSet rs2;
                rs2 = db.executeQuery("SELECT * FROM exerciseassigned WHERE exerciseName = '" + assignedItem + "' AND username = '" + instructor.getUsername() + "';");
                //select a modified extension date if one exists
                ResultSet extension = db.executeQuery("SELECT dueDate FROM exercisedueextended WHERE instructorUsername = '" + instructor.getUsername() + "' AND exerciseName = '" + assignedItem + "' AND studentUsername = '" + studentChosen + "';");
                if (rs2 != null && rs2.next()) {
                    if (extension != null && extension.next()) {
                        exercise = new AssignedExercise(rs2.getString("username"), rs2.getString("exerciseName"), rs2.getTimestamp(3), extension.getTimestamp("dueDate"), rs2.getDouble("score"));
                        extension.close();
                    } else {
                        exercise = new AssignedExercise(rs2.getString("username"), rs2.getString("exerciseName"), rs2.getTimestamp(3), rs2.getTimestamp(4), rs2.getDouble("score"));
                    }
                }
                rs2.close();

                //select the student score for the exercise
                ResultSet rs3;
                rs3 = db.executeQuery("SELECT score FROM agslog WHERE username='" + studentChosen + "' AND exerciseName = '" + exerciseName + "';");
                if (rs3 != null && rs3.next()) {
                    totalScore = rs3.getDouble(1);
                } else {
                    totalScore = 0;
                }
                rs3.close();
            } catch (SQLException ex) {
                context.addMessage(null, new FacesMessage("Populating " + assignedItem, "ERROR"));
            }
        } else {
            context.addMessage(null, new FacesMessage("Populating...", "No Selections Made"));
        }
        
        
    }

    /**
     * Compile, run, and save the results of the assignment selected
     */
    public void compileSaveRunCode() {
        double score = 0;

        FacesContext context = FacesContext.getCurrentInstance();
        String file = exerciseName + Constant.JAVA_EXT;
        isExecuted = false;

        File newFile = new File(path + file);
        try {
            Scanner scan = new Scanner(content);
            PrintWriter out = new PrintWriter(newFile);
            while (scan.hasNextLine()) {
                out.println(scan.nextLine());
            }
            out.close();
        } catch (FileNotFoundException ex) {
            context.addMessage(null, new FacesMessage("ERROR:", "File not saved!"));
        }

        Output output = CommandSynchronized.compileProgram(Constant.COMPILE, path, file);
        boolean isCompiled = false;

        //compile success
        if (output.error.length() < 1) {
            context.addMessage(null, new FacesMessage("Compile", "Sucess"));
            isCompiled = true;
            score = 1.0;
        } else {
            score = 0.5;
        }

        if (isCompiled) {
            score = runExercise();
        }
    }

    /**
     * Override student score
     */
    public void saveScore() {
        FacesContext context = FacesContext.getCurrentInstance();

        try (ResultSet rs4 = db.executeQuery("SELECT exerciseName from agslog WHERE username='" + studentChosen + "' AND exerciseName = '" + exerciseName + "';")) {

            //if student has already submitted work, update score
            if (rs4.next()) {
                db.executeStatement("UPDATE agslog SET score = " + totalScore + " WHERE username = '" + studentChosen + "' AND exerciseName = '" + rs4.getString("exerciseName") + "';");
            //else, insert new score
            } else {
                ps.setString(1, studentChosen);
                ps.setString(2, exerciseName);
                ps.setString(3, "");
                ps.setDouble(4, totalScore);
                java.util.Date today = new java.util.Date();
                ps.setTimestamp(5, new java.sql.Timestamp(today.getTime()));
                ps.execute();
            }
        } catch (SQLException ex) {
            context.addMessage(null, new FacesMessage("Modified score for " + studentChosen, assignedItem + ": FAILED"));

        }
        context.addMessage(null, new FacesMessage("Modified score for " + studentChosen, assignedItem + ": " + totalScore));

    }

    /**
     * Extends the due date of individual student's assigned exercise
     */
    public void extendDate() {
        FacesContext context = FacesContext.getCurrentInstance();

        try {
            ResultSet select;
            java.util.Date today = new java.util.Date();
            select = db.executeQuery("SELECT * FROM exercisedueextended WHERE instructorUsername = '" + instructor.getUsername() + "' AND exerciseName = '" + assignedItem + "' AND studentUsername = '" + studentChosen + "';");

            //if the exercise has already been extended once, then update
            if (select != null && select.next()) {
                PreparedStatement update = db.getPreparedStatement("UPDATE exercisedueextended SET dueDate=?, whenChanged=? WHERE instructorUsername=? AND exerciseName=? AND studentUsername=?;");
                update.setTimestamp(1, new java.sql.Timestamp(date.getTime()));
                update.setTimestamp(2, new java.sql.Timestamp(today.getTime()));
                update.setString(3, instructor.getUsername());
                update.setString(4, assignedItem);
                update.setString(5, studentChosen);
                update.execute();
                select.close();
            //else, insert
            } else {
                PreparedStatement insert = db.getPreparedStatement("INSERT INTO exercisedueextended VALUES(?, ?, ?, ?, ?);");
                insert.setTimestamp(5, new java.sql.Timestamp(date.getTime()));
                insert.setTimestamp(4, new java.sql.Timestamp(today.getTime()));
                insert.setString(1, instructor.getUsername());
                insert.setString(2, assignedItem);
                insert.setString(3, studentChosen);
                insert.execute();
            }
        } catch (SQLException ex) {
            context.addMessage(null, new FacesMessage("Extention for " + studentChosen, assignedItem + ": FAILED"));
        }
        exercise = exercise.extendDate(date);
        context.addMessage(null, new FacesMessage("Extention for " + studentChosen, assignedItem + ": " + date.toString()));
    }

    /**
     * Grades an external .class file by comparing output of student's
     * program, given arbitrary input, to a key
     * 
     * @return A score of 1 or the max score the assigned exercise allows
     */
    public double runExercise() {
        isExecuted = true;
        classFile = new File(path + exerciseName + Constant.CLASS_EXT);

        run1 = "";
        run2 = "";
        run3 = "";

        if (exercise.isPastDue()) {
            run1 = "<div id=\"boxstyle\" ; style=\"width: 100%; text-align: left; border-color: blue; margin-left: 0em; margin-right: 0em; margin-top: 0em\"><h3 id=\"h3style\" ; style=\"background-color: blue; color: white;  text-align: left; padding-left: 1em\">Run Status</h3>  <div style = \"margin-left:1em\"><font face=\"Courier New\">";
            run1 += "Past due.";
            return 1.0;
        }

        if (!assignment.getGradable()) {
            run1 = "<div id=\"boxstyle\" ; style=\"width: 100%; text-align: left; border-color: blue; margin-left: 0em; margin-right: 0em; margin-top: 0em\"><h3 id=\"h3style\" ; style=\"background-color: blue; color: white;  text-align: left; padding-left: 1em\">Run Status</h3>  <div style = \"margin-left:1em\"><font face=\"Courier New\">";
            run1 += "Assignment can compile and submit but isn't gradable";
            return 1.0;
        } else {
            String inputFileA = Constant.AGS_ROOT + Constant.GRADE_EXERCISE + exerciseName + 'a' + ".input";
            String studentOutputFile = path + exerciseName + ".out";

            //if there is no input file
            if (!new File(inputFileA).exists()) {
                //run it with no input
                Output executeProgram = executeAProgram(instructor.getCourseId(), studentChosen, null, exerciseName);
                if (executeProgram.error.equals("error")) {
                    return 1.0;
                } else {
                    //student's output
                    String output = Utility.readFile(studentOutputFile);

                    //read key
                    String keyFile = Constant.AGS_ROOT + Constant.GRADE_EXERCISE + exerciseName + ".output";
                    String instructorOutput = Utility.readFile(keyFile);

                    //compare and grade
                    RunResult run = RunResult.gradeOneRun(output, instructorOutput, 0, executeProgram.timeUsed);
                    run1 = run.outForOneRun;
                    if (run.isCorrect) {
                        return Double.parseDouble(exercise.getScore());
                    } else {
                        return 1.0;
                    }
                }
                //input does exist
            } else {
                for (int i = 'a'; i < 'd'; i++) {
                    String file = Constant.AGS_ROOT + Constant.GRADE_EXERCISE + exerciseName + (char) i + ".input";
                    if (new File(file).exists()) {
                        //input a
                        Output executeProgram = executeAProgram(instructor.getCourseId(), studentChosen, file, exerciseName);
                        if (executeProgram.error.equals("error")) {
                            return 1.0;
                        } else {
                            //student's output
                            String output = Utility.readFile(studentOutputFile);

                            //read key
                            String keyFile = Constant.AGS_ROOT + Constant.GRADE_EXERCISE + exerciseName + (char) i + ".output";

                            String instructorOutput = Utility.readFile(keyFile);

                            //compare and grade
                            RunResult run = RunResult.gradeOneRun(output, instructorOutput, 0, executeProgram.timeUsed);
                            switch (i) {
                                case 'a':
                                    run1 = run.outForOneRun;
                                    break;
                                case 'b':
                                    run2 = run.outForOneRun;
                                    break;
                                case 'c':
                                    run3 = run.outForOneRun;
                                    break;
                            }
                            if (!run.isCorrect) {
                                return 1.0;
                            }
                        }
                        //input file doesn't exist
                    } else {
                        break;
                    }
                }//end for
                return Double.parseDouble(exercise.getScore());
            }
        }
    }

    /**
     * Runs a .class file with an input
     * 
     * @param studentCourseId -- Student's course id
     * @param studentUsername -- Student's username
     * @param inputFilename -- input file name
     * @param exerciseName -- exercise name
     * @return 
     */
    private CommandSynchronized.Output executeAProgram(String studentCourseId, String studentUsername, String inputFilename, String exerciseName) {
        String programDirectory = Constant.AGS_ROOT + studentCourseId + "/" + studentUsername;
        //CommandSynchronized.Output result = CommandSynchronized.runACommand(command, "java5");

        if (inputFilename == null) {
            return CommandSynchronized.executeProgram(Constant.EXECUTE, exerciseName, programDirectory, null, programDirectory + "/" + exerciseName + ".out");
        } else {
            return CommandSynchronized.executeProgram(Constant.EXECUTE, exerciseName, programDirectory, inputFilename, programDirectory + "/" + exerciseName + ".out");
        }
    }

    public static void main(String[] args) {
    }
}